package model;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import constants.Events;
import core.EventBus;

import model.playingfield.Piece;

/**
 * @author christopher
 * This is the main interface class for the Model part of our abstraction.
 * It handles all interactions with the View.
 */

public class Model implements PropertyChangeListener{

	private transient final List<PropertyChangeListener> listeners = new ArrayList<PropertyChangeListener>();
	
	private Die d = new Die();
	private int die = -1;
	private Player[] players;
	private int turn;
	private int timesMoved = 0;
	private TurnTimer tt;
	private boolean game = false;
	private Piece[] fields = new Piece[40];
	private Piece[][] pieces = new Piece[4][4];
	private Piece[][] homes = new Piece[4][4];
	private EventBus eb;
	
	public Model(EventBus eb) {
		this.eb = eb;
	}

	/**
	 * change turn to the next player
	 */
	protected void changeTurn(){
		if(turn >= 3)
			turn = 0;
		else
			turn++;
		resetDie();
		timesMoved = 0;
		eb.handleEvent("Turn", null, players[turn]);
	}
	
	/**
	 * Is the turn finished?
	 * @return
	 */
	private boolean turnCheck(){
		if(die > 0 && timesMoved == 1)
			return true;
		return false;
	}

	private void newPlayerName(int playerNr, String name) {
		System.out.println(playerNr + "  "  + name);
		String oldName = players[playerNr].getPlayerName();
		players[playerNr].setPlayerName(name);
		eb.handleEvent("P"+(playerNr+1)+"Name", oldName, name);
	}

	private void reset(){
		eb.handleEvent("Reset",null,"lol");
	}


	/**
	 * Should this method be in model.playingfield.(Piece.java)?
	 * If there are more pieces at the same index, they might be set to null
	 * See fields[j] = null;
	 * @param i the number of the piece to move. Accepted values are: (0-3),(10-13),(20-23),(30-33)
	 */
	private void movePiece(int p){
		String ps = ((Integer) p).toString(); //Allows us to later separate the two digits

		//Only attempt to move a piece if the current player has thrown the die. 
		if(die > 0){
			int i = p%10; //Calculate the piece's index

			//If the first digit == the index of the current player 
			//OR there is only one digit and it's the first player's turn
			//Then the piece belongs to the player, in all other cases:
			if(!(ps.charAt(0) == ((Integer)turn).toString().charAt(0) || ps.length() == 1 && turn == 0)){
				//The player clicked someone else's piece
				eb.handleEvent("Error",null,"Can't touch this, na-nanana!");
				//Go no further.
				return; 
			}

			//Now we know that the piece belongs to the right guy. But where is it located?
			//Let's check if it's on a field/spot outside the starting area
			for (int j = 0; j < fields.length; j++){
				if(fields[j] != null)	//What have we here?
					if(fields[j] == pieces[turn][i]){   //The piece we want to move!
						int nf = nextValidPosition(j);     //Where can we move it?
						if(nf != -1){					//Can we move it?
							if(nf != j){				//Is there a point in moving it?
								fields[nf] = pieces[turn][i];   //Move
								fields[j] = null;               //Remove from previous location
								System.out.println("move");     
								eb.handleEvent("P"+(turn+1)+"Piece", j, nf);
								timesMoved ++;                  
								return; //The piece has been moved. Stop here.
							}else{
								timesMoved ++; //Still counts as a move.
								return; //The piece is not supposed to move.
							}
						}else{
							if(!winGame(turn)){
								int h = toHome(j);
								if(homes[turn][h] == null){
									homes[turn][h] = fields[j]; 
									fields[j] = null;
									timesMoved ++;
									System.out.println("BEFORE home");
									eb.handleEvent("P"+(turn+1)+"Home", j, h);
								}
							}
							return; //The piece was found but is not allowed to move. Or moved to home.
						}
					}
			}//End for-loop

			//Starting areas are not logically represented yet, but
			//the piece must be in one. Let's move it to it's first spot.
			fields[turn*10] = pieces[turn][i];
			eb.handleEvent("P"+(turn+1)+"Piece", -1, turn*10);
		}
		else
			eb.handleEvent("Error", null, "Throw the die first!");
	}

	private int toHome(int from){
		int to = die;
		int last = (turn*10)-1;
		if(last == -1)
			last = 39;
		int diff = last - from;
		to -= diff;
		if(to > 4)
			return from;
		return to;
	}

	private boolean winGame(int p){
		for(int i = 0; i < homes.length; i++){
			if(homes[p][i] == null){
				return false;
			}
		}
		return true;
	}

	private void initPieces(){
		for(int i = 0; i < pieces.length; i ++){
			for(int j = 0; j < pieces.length; j++){
				pieces[i][j] = new Piece(players[i]);
				eb.handleEvent("P"+(i+1)+"PIECE", null, (i*10)+j);
			}
		}
	}

	private boolean collision(int from, int to){ 
		return fields[to] != null;		
	}

	/**
	 * Given the current position "i", this method calculates which the next valid position is.
	 * @param i The position of a piece
	 * @return return i or a corresponding valid field index
	 *         returns -1 if there are no legal moves
	 */
	private int nextValidPosition(int i){
		int nf = (i+die)%40;
		switch(turn){
		case 0: if(i > 6  && nf < 6 ){System.out.println("eeea"); return -1;};
		break;
		case 1: if(i < 10 && nf > 10){System.out.println("eeeb"); return -1;};
		break;
		case 2: if(i < 20 && nf > 20){System.out.println("eeec"); return -1;};
		break;
		case 3: if(i < 30 && nf > 30){System.out.println("eeed"); return -1;};
		break;
		}

		if(collision(i,nf)){
			System.out.println("Collision!");
			return i;
		}
		/*
			0 : 39-0
			1 : 39-30
			2 : 39-20
			3 : 39-10
		 */
		return nf; 
	}

	/**
	 * Prepares the model and starts a new game
	 */
	private void newGame(){
		tt = new TurnTimer();
		game = true;
		resetDie();
		players = new Player[4];
		for(int i = 0; i < 4; i++){
			players[i] = new Player("Player"+(i+1), Color.RED);
			eb.handleEvent("P"+(i+1)+"Name", null, players[i]);
		}
		initPieces();
		turn = 0;
		eb.handleEvent("Turn", null, players[turn]);
		tt.start();
	}

	private void resetDie(){
		eb.handleEvent("Die", -1, die = 0);
	}

	private void rollDie(){
		if(die < 1)
			eb.handleEvent("Die", die, die = d.throwDie());
	}

	//End of test

	@Override
	public void propertyChange(PropertyChangeEvent e) {
		System.out.println("Model got the package: "+e.getPropertyName());
		System.out.println("Model got the package: "+e.getNewValue());
		
		if (game){
			switch(Events.valueOf(e.getPropertyName())){
			case THROW: rollDie(); 
			break;
			case MOVE: movePiece(Integer.valueOf(e.getNewValue().toString()));

			if(turnCheck())
				changeTurn();
			break;
			case NEWGAME: reset(); newGame(); 
			break;
			case PLAYERNAME: newPlayerName(turn, e.getSource().toString());
			break;
			}
		}
		else{
			switch(Events.valueOf(e.getPropertyName())){
			case NEWGAME: newGame(); 
			break;
			case ERROR:
				eb.handleEvent("Error", null, "Couldn't parse the ActionEvent");
				break;
			default: 
				eb.handleEvent("Error", null, "You must start a new game!");
			}
		}		
	}
	
	
	public void addPropertyChangeListener (PropertyChangeListener listener) {
		listeners.add(listener);
	}

	public void removePropertyChangeListener (PropertyChangeListener listener) {
		listeners.remove(listener);
	}

	private void firePropertyChange (String property, Object oldValue, Object newValue) {

		if (oldValue == newValue || oldValue != null && oldValue.equals(newValue)) {
			return;
		}

		PropertyChangeEvent evt = new PropertyChangeEvent(this, property, oldValue, newValue);
		for (PropertyChangeListener listener : new ArrayList<PropertyChangeListener>(listeners)) {
			listener.propertyChange(evt);
		}
	}
}